/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package pt.utl.ist.isr.livetrack.server.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.apache.log4j.Logger;
import pt.utl.ist.isr.livetrack.analysis.ImageUtils;
import pt.utl.ist.isr.livetrack.server.LTServer;
import pt.utl.ist.isr.livetrack.server.LTServerImpl;
import pt.utl.ist.isr.livetrack.server.interfaces.Blob;
import pt.utl.ist.isr.livetrack.server.interfaces.Camera;
import pt.utl.ist.isr.livetrack.server.interfaces.Tracker;
import pt.utl.ist.isr.livetrack.tracker.mht.MHTracker;
import pt.utl.ist.isr.livetrack.tracker.mht.TDetection;

/**
 *
 * @author David Miguel Antunes <davidmiguel [ at ] antunes.net>
 */
public class TrackerImpl implements Tracker {

    private static final Logger logger = Logger.getLogger(TrackerImpl.class);
    private MHTracker<Blob> tracker;
    private boolean running = false;
    private LTServerImpl server;
    private long stepTime = -1;
    private long nSteps = 0;
    // === Tracker settings ===
    private int maxNumLeaves = 10;
    private int maxDepth = 10;
    private int maxTimeUndetected = 30 * 1000;
    private int bestKHypGenerated = 5;
    private double probUndetected = 0.01;
    private double probNewTarget = 0.001;
    private double probFalseAlarm = 0.01;
    private double probChangeZone = 0.5;
    private int gateSize = 500;
    private double featuresUpdateRate = 1;
    private int featuresDifferencePenalization = 10;
    private int histogramMaxDepth = 3;
    private int histogramNBins = 3;
    private ExecutorService eservice = Executors.newFixedThreadPool(10);

    public TrackerImpl() {
    }

    public TrackerImpl(LTServerImpl server) {
        this.server = server;
    }

    @Override
    public void start() {
        tracker = new MHTracker(
                maxNumLeaves,
                maxDepth,
                maxTimeUndetected,
                bestKHypGenerated,
                probUndetected,
                probNewTarget,
                probFalseAlarm,
                probChangeZone,
                gateSize,
                featuresUpdateRate,
                featuresDifferencePenalization,
                false);
        stepTime = -1;
        nSteps = 0;
        running = true;
    }

    @Override
    public void stop() {
        tracker = null;
        running = false;
    }

    @Override
    public boolean isRunning() {
        return running;
    }

    @Override
    public void step() {
        long time = System.currentTimeMillis();
        Map<String, Camera> cameras = server.getCamerasLocal();
        Map<Blob, String> blobs = new HashMap<Blob, String>();

        List<Future<Map<Blob, String>>> futuresList = new ArrayList<Future<Map<Blob, String>>>();

        for (final Camera camera : cameras.values()) {
            futuresList.add(eservice.submit(new Callable<Map<Blob, String>>() {

                @Override
                public Map<Blob, String> call() throws Exception {
                    camera.acquireImage();
                    Map<Blob, String> map = new HashMap<Blob, String>();
                    for (Blob blob : camera.getBlobs()) {
                        map.put(blob, camera.getId());
                    }
                    return map;
                }
            }));
        }

        for (Future<Map<Blob, String>> future : futuresList) {
            try {
                blobs.putAll(future.get());
            } catch (InterruptedException e) {
            } catch (ExecutionException e) {
            }
        }

        ArrayList<TDetection<Blob>> detections = new ArrayList<TDetection<Blob>>();

        for (Map.Entry<Blob, String> entry : blobs.entrySet()) {
            Blob blob = entry.getKey();
            Camera camera = cameras.get(entry.getValue());

            logger.debug("Blob: " + blob);

            int[] featuresInts = new ImageUtils(
                    camera.getWidth(),
                    camera.getHeight(),
                    3).extractHistogramTree(camera.getImageAsIntArray(), blob, 3, 3);

            double[] features = new double[featuresInts.length];
            for (int i = 0; i < features.length; i++) {
                features[i] = featuresInts[i];
            }

            features = new double[]{1, 0, 1};

            TDetection<Blob> detection = new TDetection<Blob>(features, blob, server.getPerimeterLocal().getZonesLocal().get(camera.getZoneId()));
            detections.add(detection);
        }

        tracker.newScan(System.currentTimeMillis(), detections);
        if (stepTime != -1) {
            stepTime = (long) (stepTime * 0.9 + (System.currentTimeMillis() - time) * 0.1);
        } else {
            stepTime = System.currentTimeMillis() - time;
        }

        nSteps++;

        logger.debug("Tracker step took " + stepTime + "ms");
    }

    @Override
    public Map<Long, Blob> getTargets() {
        return tracker.getBestHypothesisTargets();
    }

    public void setServer(LTServerImpl server) {
        this.server = server;
    }

    @Override
    public int getBestKHypGenerated() {
        return bestKHypGenerated;
    }

    @Override
    public void setBestKHypGenerated(int bestKHypGenerated) {
        this.bestKHypGenerated = bestKHypGenerated;
    }

    @Override
    public int getFeaturesDifferencePenalization() {
        return featuresDifferencePenalization;
    }

    @Override
    public void setFeaturesDifferencePenalization(int featuresDifferencePenalization) {
        this.featuresDifferencePenalization = featuresDifferencePenalization;
    }

    @Override
    public double getFeaturesUpdateRate() {
        return featuresUpdateRate;
    }

    @Override
    public void setFeaturesUpdateRate(double featuresUpdateRate) {
        this.featuresUpdateRate = featuresUpdateRate;
    }

    @Override
    public int getGateSize() {
        return gateSize;
    }

    @Override
    public void setGateSize(int gateSize) {
        this.gateSize = gateSize;
    }

    @Override
    public int getMaxDepth() {
        return maxDepth;
    }

    @Override
    public void setMaxDepth(int maxDepth) {
        this.maxDepth = maxDepth;
    }

    @Override
    public int getMaxNumLeaves() {
        return maxNumLeaves;
    }

    @Override
    public void setMaxNumLeaves(int maxNumLeaves) {
        this.maxNumLeaves = maxNumLeaves;
    }

    @Override
    public int getMaxTimeUndetected() {
        return maxTimeUndetected;
    }

    @Override
    public void setMaxTimeUndetected(int maxTimeUndetected) {
        this.maxTimeUndetected = maxTimeUndetected;
    }

    @Override
    public double getProbChangeZone() {
        return probChangeZone;
    }

    @Override
    public void setProbChangeZone(double probChangeZone) {
        this.probChangeZone = probChangeZone;
    }

    @Override
    public double getProbFalseAlarm() {
        return probFalseAlarm;
    }

    @Override
    public void setProbFalseAlarm(double probFalseAlarm) {
        this.probFalseAlarm = probFalseAlarm;
    }

    @Override
    public double getProbNewTarget() {
        return probNewTarget;
    }

    @Override
    public void setProbNewTarget(double probNewTarget) {
        this.probNewTarget = probNewTarget;
    }

    @Override
    public double getProbUndetected() {
        return probUndetected;
    }

    @Override
    public void setProbUndetected(double probUndetected) {
        this.probUndetected = probUndetected;
    }

    @Override
    public int getHistogramMaxDepth() {
        return histogramMaxDepth;
    }

    @Override
    public void setHistogramMaxDepth(int histogramMaxDepth) {
        if (!running) {
            this.histogramMaxDepth = histogramMaxDepth;
        }
    }

    @Override
    public int getHistogramNBins() {
        return histogramNBins;
    }

    @Override
    public void setHistogramNBins(int histogramNBins) {
        if (!running) {
            this.histogramNBins = histogramNBins;
        }
    }

    @Override
    public long getStepTime() {
        return stepTime;
    }

    @Override
    public long getNSteps() {
        return nSteps;
    }
}
